﻿using System.IO;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using Sai.Core.Rules;
using Sai.Core;
using Sai.Cecil;
using Sai.Core.Reporting;
using System.Reflection;
using System.Collections.Generic;

namespace Sai.Tasks
{
 	/// <summary>
	/// MsBuild task.
	/// Example:
	/// <UsingTask AssemblyFile="$(MSBuildExtensionsPath)\Sai.Tasks.dll"
	///			TaskName="Sai"/>
	/// <Target Name="Demo">
	///		<Sai 
	///			ProjectFile="MyProject.Sai" 
	///			ProductName="My Project"
	///			FromVersion="2011"
	///			FromFolder="" or FromAssemblies="@(FromDlls)"
	///			ToVersion="2012"
    ///			ToFolder="" or ToAssemblies="@(ToDlls)"
	///			OutputFolder=""
	///			OutputFormat="MAML"
	///			SummaryId="12345"
	///			SandcastleProject="..."
	///			TopicId="...">
	///		</Sai>
	///	</Target>
	/// </summary>
	public class Sai : Task
	{
        public string ProductName { get; set; }

        public ITaskItem ProjectFile { get; set; }

		public string FromVersion { get; set; }

		public ITaskItem FromFolder { get; set; }

        public ITaskItem[] FromAssemblies { get; set; }

		public string ToVersion { get; set; }
		
		public ITaskItem ToFolder { get; set; }

        public ITaskItem[] ToAssemblies { get; set; }

		public ITaskItem OutputFolder { get; set; }

		public string OutputFormat { get; set; }

		public ITaskItem SandcastleProject { get; set; }

		public string TopicId { get; set; }

        public bool Quiet { get; set; }

        public bool Clean { get; set; }

        public bool ReportOnAdditions { get; set; }

        public bool ReportOnChanges { get; set; }

        public bool ReportOnRemovals { get; set; }

        public bool ReportOnObsolete { get; set; }

        public Sai()
        {
            this.ReportOnAdditions = true;
            this.ReportOnChanges = true;
            this.ReportOnRemovals = true;
            this.ReportOnObsolete = true;
        }

        public override bool Execute()
        {
            if (!this.ValidateParameters())
                return false;

            IRulePluginFinder ruleFinder = new RulePluginReflectionFinder(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
            IRulePluginRepository repository = new RulePluginRepository();

            ruleFinder.Find(repository);

            ProductReleaseProjectBuilder builder = new ProductReleaseProjectBuilder();
            ProductReleaseProject project = null;

            string projectFilePath = string.Empty;
            
            if (this.ProjectFile != null)
                projectFilePath = this.ProjectFile.GetMetadata("FullPath");

            if (!string.IsNullOrEmpty(projectFilePath))
            {
                project = builder.CreateFromFile(projectFilePath);
            }
            else
            {
                string outputFolderPath = string.Empty;

                if (this.OutputFolder != null)
                    outputFolderPath = this.OutputFolder.GetMetadata("FullPath");

                if (string.IsNullOrEmpty(outputFolderPath))
                {
                    outputFolderPath = Path.Combine(
                        System.Environment.GetFolderPath(
                            System.Environment.SpecialFolder.LocalApplicationData),
                        "Sai");
                }

                string fromFolder = string.Empty;

                if (fromFolder != null)
                {
                    this.FromFolder.GetMetadata("FullPath");
                }

                string toFolder = string.Empty;

                if (this.ToFolder != null)
                {
                    toFolder = this.ToFolder.GetMetadata("FullPath");
                }

                project = builder.Create(this.ProductName,
                                            new Version ("1.0.0.0"),
                                            this.FromVersion,
                                            fromFolder,
                                            this.ToVersion,
                                            toFolder,
                                            outputFolderPath,
                                            (ReportFormat) Enum.Parse(typeof(ReportFormat), this.OutputFormat, true),
                                            this.Clean,
                                            this.SandcastleProject.GetMetadata("FullPath"),
                                            this.TopicId,
                                            this.ReportOnRemovals,
                                            this.ReportOnAdditions,
                                            this.ReportOnChanges,
                                            this.ReportOnObsolete);
                
                if (this.FromAssemblies.Length > 0)
                {
                    List<string> oldAssemblies = new List<string>();

                    for (int i = 0; i < this.FromAssemblies.Length; ++i)
                    {
                        ITaskItem item = this.FromAssemblies[i];
                        string dll = item.GetMetadata("FullPath");

                        if (File.Exists(dll))
                        {
                            oldAssemblies.Add(dll);
                        }
                    }

                    project.AddFromFiles(oldAssemblies);
                }

                if (this.ToAssemblies.Length > 0)
                {
                    List<string> newAssemblies = new List<string>();

                    for (int i = 0; i < this.ToAssemblies.Length; ++i)
                    {
                        ITaskItem item = this.ToAssemblies[i];
                        string dll = item.GetMetadata("FullPath");

                        if (File.Exists(dll))
                        {
                            newAssemblies.Add(dll);
                        }
                    }

                    project.AddToFiles(newAssemblies);
                }
            }

            // now given project run it...
            var folderComparisonBuilder = new AssemblyComparisonBuilder(project);

            var progressIndicator = new NullProgressReporter();

            // comparison results needs packaging with summary info and changed assemblies to report on it.
            var comparisonResults = folderComparisonBuilder.Build(progressIndicator);

            IWalkTypeDifferencesFactory typeDifferenceWalkerFactory = new TypeDifferenceWalkerFactory(repository);
            IWalkIntrospectedAssembliesFactory assemblyDifferenceWalkerFactory = new AssemblyIntrospectionWalkerFactory(repository);
            IProductReleaseDifferenceFactory releaseDifferenceFactory = new ProductReleaseDifferenceFactory(project);

            IAssemblyDifferenceFactory assemblyDifferenceFactory = new AssemblyDifferenceFactory();

            var introspectionBuilder = new TypeIntrospectionBuilder(
                                        new MonoCecilAssemblyIntrospector(),
                                        assemblyDifferenceWalkerFactory,
                                        typeDifferenceWalkerFactory,
                                        releaseDifferenceFactory,
                                        assemblyDifferenceFactory,
                                        comparisonResults,
                                        project);

            introspectionBuilder.Build(progressIndicator);

            IReportFormatterFactory reportFactory = new ReportFormatterFactory(project);

            if (introspectionBuilder.ProductReleaseDifference != null)
            {
                var reporter = new ReportBuilder(
                    introspectionBuilder.ProductReleaseDifference,
                    introspectionBuilder.AssemblyDifferences,
                    project,
                    reportFactory);

                reporter.Build(progressIndicator);
            }

            return true;
        }

        private bool ValidateParameters()
        {
            this.Log.LogMessageFromText("Validating arguments", MessageImportance.Low);

            // specify a project file or individual arguments
            if (this.ProjectFile != null)
            {
                if (!this.ValidateFile(this.ProjectFile))
                {
                    return false;
                }
            }
            else
            {
                if (this.FromFolder != null && !this.ValidateFolder(this.FromFolder))
                {
                    return false;
                }

                if (this.ToFolder != null && !this.ValidateFolder(this.ToFolder))
                {
                    return false;
                }

                if (this.OutputFolder != null && !this.ValidateFolder(this.OutputFolder))
                {
                    return false;
                }

                if (!string.IsNullOrEmpty(this.OutputFormat))
                {
                    if (!this.OutputFormatIsCorrect(this.OutputFormat))
                    {
                        string message = string.Format("Output format \'{0}\' is not recognized.", this.OutputFormat);
                        this.Log.LogError(message, null);

                        this.Log.LogError("Available output formats are :", null);

                        foreach (ReportFormat format in Enum.GetValues(typeof(ReportFormat)))
                        {
                            this.Log.LogError(format.ToString(), null);
                        }

                        return false;
                    }
                }

                if (this.SandcastleProject != null)
                {
                    if (!this.ValidateFile(this.SandcastleProject))
                    {
                        return false;
                    }
                }

                if (!string.IsNullOrEmpty(this.TopicId))
                {
                    // needs to be a guid...
                    try
                    {
                        new Guid(this.TopicId);
                    }
                    catch (FormatException)
                    {
                        string message = string.Format("Topic Id \'{0}\'is not a valid guid", this.TopicId);
                        this.Log.LogError(message, null);
                        return false;
                    }
                }
            }

            return true;
        }

        private bool OutputFormatIsCorrect(string format)
        {
            const bool ignoreCase = true;

            string upperCaseFormat = format.ToUpper();

            foreach (ReportFormat reportOption in Enum.GetValues(typeof(ReportFormat)))
            {
                if (string.Compare(upperCaseFormat, reportOption.ToString().ToUpper(), ignoreCase) == 0)
                    return true;
            }

            return false;
        }

        private bool ValidateFile(ITaskItem file)
        {
            if (file == null)
                return false;

            string path = file.GetMetadata("FullPath");

            if (!File.Exists(path))
            {
                string message = string.Format("File \'{0}\' does not exist.", path);
                this.Log.LogError(message, null);
                return false;
            }

            return true;
        }

        private bool ValidateFolder(ITaskItem folder)
        {
            string path = folder.GetMetadata("FullPath");

            if (!Directory.Exists(path))
            {
                string message = string.Format("Folder \'{0}\' does not exist.", path);
                this.Log.LogError(message, null);
                return false;
            }

            return true;
        }
	}
}
